//
// Created by Tony on 2024/6/14.
//

#include "Interpreter.h"

#include "../../compiler/CodeGenerator.h"
#include "../../compiler/SymbolFinder.h"
#include "../../lexer/Lexer.h"
#include "../../parser/ClassParser.h"
#include "../../parser/ModuleParser.h"
#include "../instructions/InstructionFactory.h"
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>

namespace Runtime {

using std::cout;
using std::endl;
using std::make_shared;
using std::setw;

shared_ptr<RtData::Frame> Interpreter::interpretStatement(
    const shared_ptr<RtCode::ClassTable>& classTable, const string& code) {

    Lexer::Lexer lexer;
    Lexer::TokenQueue tokenQueue;
    if (!lexer.parseString(code, 0, tokenQueue)) {
        cout << "lexer error: " << lexer.getErrMsg() << endl;
        return nullptr;
    }

    Parser::StmtParser parser;
    parser.setTokenQueue(tokenQueue);
    auto ast = parser.parse();
    if (ast == nullptr) {
        cout << "parser error: " << parser.getErrMsg() << endl;
        return nullptr;
    }

    return interpretModule(classTable, ast);
}

shared_ptr<RtData::Frame> Interpreter::interpretModule(
    const shared_ptr<RtCode::ClassTable>& classTable, const shared_ptr<SourceFile>& sourceFile) {

    Lexer::Lexer lexer;
    Lexer::TokenQueue tokenQueue;
    if (!lexer.parseFile(sourceFile, tokenQueue)) {
        cout << "lexer error: " << lexer.getErrMsg() << endl;
        return nullptr;
    }

    Parser::ModuleParser parser;
    parser.setTokenQueue(tokenQueue);
    auto ast = parser.parse();
    if (ast == nullptr) {
        cout << "parser error: " << parser.getErrMsg() << endl;
        return nullptr;
    }

    return interpretModule(classTable, ast);
}

shared_ptr<RtData::Frame> Interpreter::interpretModule(
    const shared_ptr<RtCode::ClassTable>& classTable, const shared_ptr<Parser::Ast>& ast) {

    using Compiler::ScopeType;
    using Compiler::SymbolTable;

    auto clinitMethod = make_shared<RtCode::Method>(CLASS_INIT_METHOD_NAME);
    auto clazz = make_shared<RtCode::Class>(ENTRY_CLASS_NAME, classTable);
    clazz->addMethod(clinitMethod);
    classTable->addClass(clazz);

    auto classSymbolTable =
        make_shared<SymbolTable>(ScopeType::CLASS, 0, nullptr, ENTRY_CLASS_NAME);
    auto clinitMethodSymbolTable =
        make_shared<SymbolTable>(ScopeType::METHOD, 0, classSymbolTable, CLASS_INIT_METHOD_NAME);
    classSymbolTable->addInner(clinitMethodSymbolTable);

    Compiler::SymbolFinder symbolFinder(clazz, classTable, classSymbolTable,
                                        clinitMethodSymbolTable);
    if (symbolFinder.visit(ast.get()) == -1) {
        cout << "symbol finder error: " << symbolFinder.getErrMsg() << endl;
        return nullptr;
    }
    clinitMethod->setLocalSlotCount(clinitMethodSymbolTable->maxSlotCount());

    Compiler::CodeGenerator codeGenerator(clazz, classTable, clinitMethod);
    if (codeGenerator.visit(ast.get()) == -1) {
        cout << "code generator error: " << codeGenerator.getErrMsg() << endl;
        return nullptr;
    }
    clinitMethod->getCode()->addOpCode(OpCode::RETURN);

    return loop(classTable);
}

std::shared_ptr<RtData::Frame> Interpreter::interpretClass(
    const shared_ptr<RtCode::ClassTable>& classTable, const shared_ptr<SourceFile>& sourceFile) {

    Lexer::Lexer lexer;
    Lexer::TokenQueue tokenQueue;
    if (!lexer.parseFile(sourceFile, tokenQueue)) {
        cout << "lexer error: " << lexer.getErrMsg() << endl;
        return nullptr;
    }

    Parser::ClassParser parser;
    parser.setTokenQueue(tokenQueue);
    auto ast = parser.parse();
    if (ast == nullptr) {
        cout << "parser error: " << parser.getErrMsg() << endl;
        return nullptr;
    }

    return interpretClass(classTable, ast);
}

std::shared_ptr<RtData::Frame> Interpreter::interpretClass(
    const shared_ptr<RtCode::ClassTable>& classTable, const shared_ptr<Parser::Ast>& ast) {

    Compiler::SymbolFinder symbolFinder(classTable);
    if (symbolFinder.visit(ast.get()) == -1) {
        cout << "symbol finder error: " << symbolFinder.getErrMsg() << endl;
        return nullptr;
    }

    Compiler::CodeGenerator codeGenerator(classTable);
    if (codeGenerator.visit(ast.get()) == -1) {
        cout << "code generator error: " << codeGenerator.getErrMsg() << endl;
        return nullptr;
    }

    return loop(classTable);
}

std::shared_ptr<RtData::Frame> Interpreter::loop(const shared_ptr<RtCode::ClassTable>& classTable) {
    ByteCodeReader reader;

    auto clazz = classTable->getClassByName(ENTRY_CLASS_NAME);
    if (clazz == nullptr) {
        return nullptr;
    }
    cout << endl << classTable->toString() << endl;

    auto stack = make_shared<RtData::Stack>(8);
    auto method = clazz->getMainMethod();
    if (method == nullptr) {
        method = clazz->getClassInitMethod();
    }
    stack->push(make_shared<RtData::Frame>(stack, method));

    shared_ptr<RtData::Frame> frame;

    while (true) {
        frame = stack->top();
        auto pc = frame->getNextPC();
        auto top = frame->getTop();
        stack->setPC(pc);

        reader.reset(frame->getMethod()->getCode(), pc);
        auto opcode = reader.readOpCode();
        auto* instr = InstructionFactory::create(opcode);
        instr->fetchOperands(reader);
        frame->setNextPC(reader.getNextPC());
        instr->execute(*frame);

        printDebugInfo(pc, top, frame);

        if (stack->isEmpty()) {
            break;
        }
    }

    return frame;
}

void Interpreter::printDebugInfo(uint32_t pc, uint16_t top, shared_ptr<RtData::Frame> frame) {
    auto classAndMethodName = string{frame->getMethod()->getOwnerClass()->getName()};
    classAndMethodName.append(string{"$"});
    classAndMethodName.append(frame->getMethod()->getName());
    cout << std::left << setw(20) << classAndMethodName << "pc: " << setw(6) << pc;
    for (int i = 0; i < frame->getMethod()->getMaxStack(); i++) {
        cout << (top == i ? ">slot:" : " slot:");
        cout << i << " = " << setw(4);
        auto slot = frame->getSlot(i);
        auto value = slot.getValue();
        switch (value.tag) {
            case RtValueTag::INT64:
                cout << value.value.int64;
                break;
            case RtValueTag::REF:
                cout << "OBJ";
                break;
            case RtValueTag::FLT64:
                cout << "FLT";
                break;
            case RtValueTag::UNKNOWN:
                cout << "NIL";
                break;
        }
    }
    cout << (top == frame->getMethod()->getMaxStack() ? " ;>" : " ;") << endl;
}

} // namespace Runtime